﻿namespace NJQuery {
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Text;
	using System.Globalization;

	/// <summary>
	/// Defines a keyed list of literal values to be rendered as a JavaScript hash on the client.
	/// </summary>
	public class PropertyCollection : IEnumerable<string>, IClientHash {


		#region Fields

		private int indent;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="PropertyCollection"/> class.
		/// </summary>
		public PropertyCollection() : this(0) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="PropertyCollection"/> class with a specified indentation level.
		/// </summary>
		/// <param name="indent">The indentation level at which the properties will be rendered as client script.</param>
		public PropertyCollection(int indent) {
			this.indent = indent;
			Items = new Dictionary<string, string>();
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets or sets the raw dictionary of named JavaScript literals added.
		/// </summary>
		protected Dictionary<string, string> Items { get; private set; }

		/// <summary>
		/// Gets the number of entries added.
		/// </summary>
		public virtual int Count {
			get { return Items.Count; }
		}

		#endregion


		#region Methods: Value

		/// <summary>
		/// Adds a literal JavaScript entry value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">The literal JavaScript value to add.</param>
		/// <exception cref="ArgumentNullException">Neither value can be null.</exception>
		/// <exception cref="ArgumentException">Neither value can be an empty string.</exception>
		protected virtual void AddItem(string name, string value) {
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Value is required.", "name");
			if(value == null) throw new ArgumentNullException("value");
			if(value.Length == 0) throw new ArgumentException("Value is required.", "value");

			Items[name] = value;
		}


		/// <summary>
		/// Adds a <see cref="Boolean"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Boolean"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, bool value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Boolean"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Boolean"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, bool[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Char"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Char"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Char"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="buffer">A <see cref="Char"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char[] buffer) {
			AddValue(name, buffer == null ? null : new string(buffer));
		}

		/// <summary>
		/// Adds a <see cref="Char"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="buffer">A <see cref="Char"/> array.</param>
		/// <param name="index">The first character to append from the array.</param>
		/// <param name="count">The number of characters to append.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char[] buffer, int index, int count) {
			AddValue(name, buffer == null ? null : new string(buffer, index, count));
		}

		/// <summary>
		/// Adds a <see cref="DateTime"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="DateTime"/> value.</param>
		/// <param name="includeTime">Whether or not to include the time portion.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, DateTime value, bool includeTime) {
			AddItem(name, value.ToClientSource(includeTime));
		}

		/// <summary>
		/// Adds a <see cref="DateTime"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="DateTime"/> array.</param>
		/// <param name="includeTime">Whether or not to include the time portion.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, DateTime[] value, bool includeTime) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource(includeTime));
		}

		/// <summary>
		/// Adds a <see cref="Decimal"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Decimal"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, decimal value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Decimal"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Decimal"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, decimal[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Double"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Double"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, double value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Double"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Double"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, double[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Single"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Single"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, float value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Single"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Single"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, float[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Int32"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Int32"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, int value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Int32"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Int32"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, int[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());			
		}

		/// <summary>
		/// Adds a <see cref="Int64"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Int64"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, long value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="Int64"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="Int64"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, long[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="String"/> value to the collection using a composite format string.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="provider">A <see cref="IFormatProvider"/> that supplies culture-specific formatting information.</param>
		/// <param name="format">A composite format string.</param>
		/// <param name="args">An object array containing zero or more objects to format.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, IFormatProvider provider, string format, params object[] args) {
			AddValue(name, provider, false, format, args);
		}

		/// <summary>
		/// Adds a <see cref="String"/> value to the collection using a composite format string.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="provider">A <see cref="IFormatProvider"/> that supplies culture-specific formatting information.</param>
		/// <param name="htmlEncode">Whether or not to encode the final value as HTML.</param>
		/// <param name="format">A composite format string.</param>
		/// <param name="args">An object array containing zero or more objects to format.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, IFormatProvider provider, bool htmlEncode, string format, params object[] args) {
			AddValue(name, string.Format(provider, format, args), htmlEncode);
		}

		/// <summary>
		/// Adds a <see cref="String"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="String"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, string value) {
			AddValue(name, value, false);
		}

		/// <summary>
		/// Adds a <see cref="String"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="String"/> value.</param>
		/// <param name="htmlEncode">Encode the string value as HTML.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, string value, bool htmlEncode) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource(htmlEncode));
		}

		/// <summary>
		/// Adds a <see cref="String"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="String"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, string[] value) {
			AddValue(name, value, false);
		}

		/// <summary>
		/// Adds a <see cref="String"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="String"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		/// <param name="htmlEncode">Encode the string value as HTML.</param>
		public virtual void AddValue(string name, string[] value, bool htmlEncode) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource(htmlEncode));
		}

		/// <summary>
		/// Adds a <see cref="UInt32"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="UInt32"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, uint value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="UInt32"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="UInt32"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, uint[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="UInt64"/> value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="UInt64"/> value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, ulong value) {
			AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a <see cref="UInt64"/> array to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">A <see cref="UInt64"/> array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, ulong[] value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a null value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddNullValue(string name) {
			AddItem(name, ScriptBuilder.NullLiteral);
		}

		/// <summary>
		/// Adds a complex value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">The complex value to be added.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, IComplexProperty value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource());
		}

		/// <summary>
		/// Adds a client hash value to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">The child hash value to be added.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, IClientHash value) {
			if(value == null) AddNullValue(name);
			else AddItem(name, value.ToClientSource(indent + 1));
		}

		/// <summary>
		/// Adds a enumeration value to the collection.
		/// </summary>
		/// <typeparam name="T">The enumeration type to convert.</typeparam>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="value">The enumeration value to convert.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddEnumValue<T>(string name, T value) where T : struct {
			AddItem(name, ScriptBuilder.GetCamelCaseValue(value).ToClientSource());
		}

		/// <summary>
		/// Adds literal JavaScript code to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="script">JavaScript code.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddScript(string name, ScriptBuilder script) {
			if(script == null) throw new ArgumentNullException("script");
			AddItem(name, script.ToString());
		}

		/// <summary>
		/// Adds literal JavaScript code to the collection.
		/// </summary>
		/// <param name="name">The hash entry of the value to be provided.</param>
		/// <param name="script">JavaScript code.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddScript(string name, string script) {
			if(script == null) throw new ArgumentNullException("script");
			AddItem(name, script);
		}

		#endregion


		#region Methods: Rendering

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {
			return ToClientSource(indent);
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client, allowing an indentation level override.
		/// </summary>
		/// <param name="indent">The indentation level of the client script.</param>
		/// <returns>The client script value.</returns>
		public virtual string ToClientSource(int indent) {

			ScriptBuilder script = new ScriptBuilder();
			script.Indent = indent;
			script.OmitIndent();

			script.Append("{");

			script.Indent++;

			bool firstProperty = true;
			foreach(string property in this) {

				if(firstProperty) firstProperty = false;
				else script.Append(",");

				script.NewLine();
				script.Append(property);
			}

			script.Indent--;
			script.NewLine();

			script.Append("}");

			return script.ToString();
		}

		#endregion


		#region Methods: Emumerator

		/// <summary>
		/// Returns an enumerator that iterates through the added property values.
		/// </summary>
		/// <returns>A Key Value Pair of all JavaScript snippets added.</returns>
		public virtual IEnumerator<string> GetEnumerator() {
			foreach(KeyValuePair<string, string> item in Items)
				yield return string.Format(CultureInfo.InvariantCulture, "{0}: {1}", item.Key, item.Value);
		}

		#endregion


		#region Methods: Child Script

		/// <summary>
		/// Creates a new <see cref="ScriptBuilder"/> properly initialized to render child scripts at the correct indentation level.
		/// </summary>
		/// <returns>The new <see cref="ScriptBuilder"/> instance.</returns>
		public virtual ScriptBuilder CreateChildScript() {
			ScriptBuilder script = new ScriptBuilder();
			script.Indent = indent + 1;
			script.OmitIndent();
			return script;
		}

		#endregion


		#region Explicit Implentation: IEnumerable

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}

		#endregion


	}

}